//
//  WatchValue.swift
//  ExData
//
//  Created by 王渊鸥 on 2018/10/22.
//  Copyright © 2018 WangYuanOu. All rights reserved.
//

import Foundation

public class WatchValue<T> where T: Any {
    public typealias ValueType = T
    
    private var value:ValueType
    
    private typealias SourceAction = (source:String?, action:(ValueType, [String:Any])->(), userInfo:[String:Any])
    private var actionList:[SourceAction] = []
    
    public init(value:ValueType) {
        self.value = value
    }
    
    public func getValue() -> ValueType {
        return value
    }
    
    public func setValue(value:ValueType, source:String? = nil) {
        self.value = value
        if let source = source {
            actionList.forEach { (item) in
                if item.source != source {
                    item.action(value, item.userInfo)
                }
            }
        } else {
            actionList.forEach { (item) in
                item.action(value, item.userInfo)
            }
        }
    }
    
    public func watchValue(source:String?, action:@escaping (ValueType, [String:Any])->(), userInfo:[String:Any] = [:]) {
        actionList.append((source:source, action:action, userInfo:userInfo))
    }
}


public extension WatchValue {
    public convenience init(storage:UserDefaults = UserDefaults.standard, key:String, encoder:@escaping (ValueType)->Any, decoder:@escaping (Any)->ValueType, defaultValue:ValueType) {
        if let encodeValue = storage.value(forKey: key) {
            let value = decoder(encodeValue)
            self.init(value: value)
        } else {
            self.init(value: defaultValue)
        }
        
        watchValue(source: "userdefaults", action: { (value:ValueType, userInfo:[String : Any]) in
            guard let key = userInfo["key"] as? String,
                let storage = userInfo["storage"] as? UserDefaults,
                //let decoder = userInfo["decoder"] as? (Any)->ValueType,
                let encoder = userInfo["encoder"] as? (ValueType)->Any else { return }
            
            let encodeValue = encoder(value)
            storage.set(encodeValue, forKey: key)
            storage.synchronize()
        }, userInfo: ["key":key,
                      "storage":storage,
                      //"decoder":decoder,
                      "encoder":encoder])
    }
    
    public func bindUI(source:String, onValueChanged:@escaping (ValueType)->()) {
        watchValue(source: source, action: { (value:ValueType, userInfo:[String : Any]) in
            guard let valueChanged = userInfo["onValueChanged"] as? (ValueType)->() else { return }
            valueChanged(value)
        }, userInfo: ["onValueChanged":onValueChanged])
    }
}

public class WatchGroup {
    public var actionBody:()->()
    public var timer:TimeInterval
    private var firstState:Bool = true
    public var source:String
    
    public init(timer:TimeInterval, source:String, action:@escaping ()->()) {
        actionBody = action
        self.timer = timer
        self.source = source
    }
    
    public func runAction() {
        if firstState {
            firstState = false
            Timer.scheduledTimer(withTimeInterval: timer, repeats: false) { [weak self] (_) in
                guard let vc = self else { return }
                vc.actionBody()
                vc.firstState = true
            }
        }
    }
    
    public func addWatch<T>(value:WatchValue<T>) {
        value.watchValue(source: source, action: { (value, userInfo) in
            if let group = userInfo["group"] as? WatchGroup {
                group.runAction()
            }
        }, userInfo: ["group":self])
    }
}

fileprivate class TimerActions {
    public var list:[String:()->()] = [:]   //source : action
    public var timers:[String:Timer] = [:]
    
    public func contains(_ key:String) -> Bool {
        return list.contains(where: { $0.key == key })
    }
}

fileprivate var g_actionList:TimerActions = TimerActions()

public extension WatchValue {
    public func watchTimer(after:TimeInterval, source:String, onValueChanged:(()->())? = nil) {
        watchValue(source: source, action: { (_, _) in
            if let action = onValueChanged {
                g_actionList.list[source] = action
            }
        })
        
        if let _ = g_actionList.timers[source] {
            print("timer already exists")
        } else {
            _ = Timer.scheduledTimer(withTimeInterval: after, repeats: false) { (_) in
                if let action = g_actionList.list.removeValue(forKey: source) {
                    action()
                }
                g_actionList.timers.removeValue(forKey: source)
            }
        }
    }
}

fileprivate class CombineActions {
    public var list:[String:()->()] = [:]     //source :  action
    public var watches:[String:Int] = [:]
    
    public func contains(_ key:String) -> Bool {
        return list.contains(where: { $0.key == key })
    }
}

fileprivate var g_combineList:CombineActions = CombineActions()

public extension WatchValue {
    public func watchAll(source:String, onValueChanged:(()->())? = nil) {
        let number = g_combineList.watches[source] ?? 0
        g_combineList.watches[source] = number + 1
        
        watchValue(source: source, action: { (value, userInfo) in
            let number = g_combineList.watches[source] ?? 0
            let newNumber = number - 1
            if newNumber <= 0 {
                if let action = g_combineList.list.removeValue(forKey: source) {
                    action()
                }
                g_combineList.watches.removeValue(forKey: source)
            } else {
                g_combineList.watches[source] = newNumber
            }
        })
    }
}
